package org.bioversityinternational.mgbms.pdf;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.log4j.Logger;
import org.bioversityinternational.mgbms.model.domain.Accession;
import org.bioversityinternational.mgbms.model.domain.AssOrderAccession;
import org.bioversityinternational.mgbms.model.domain.Contact;
import org.bioversityinternational.mgbms.model.domain.Order;
import org.bioversityinternational.mgbms.text.Helper;
import org.bioversityinternational.mgbms.text.TextConstants;
import org.bioversityinternational.mgbms.ws.MgbmsService;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.AcroFields;
import com.lowagie.text.pdf.BadPdfFormatException;
import com.lowagie.text.pdf.PRAcroForm;
import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;
import com.lowagie.text.pdf.SimpleBookmark;

public class SmtaGenerator
{
	static private final Logger LOG = Logger.getLogger(SmtaGenerator.class);

	final static public String sMainFile = "SMTAe_P1_7.pdf";
	final static public String sArticle10option1File = "SMTAe_P8_signature.pdf";
	final static public String sArticle10option2File = "SMTAe_P8_shrinkwrap.pdf";
	final static public String sArticle10option3File = "SMTAe_P8_clickwrap.pdf";
	final static public String sAnnex2 = "SMTAe_P10_annexe2.pdf";
	final static public String sAnnexes3and4 = "SMTAe_P11_12_annexes3_4.pdf";
	final static public String SMTA_OUTPUT_FILENAME = "SMTA.pdf";
	
	final static private String sAddendumFile = "SMTA_addendum.pdf";
	final static public String ADDENDUM_OUTPUT_FILENAME = "SMTA_addendum.pdf";
	
	final static public String TEMPLATE_DOC_LOCATION = TextConstants.get("smta.template.folder") + (TextConstants.get("smta.template.folder").endsWith(File.separator) ? "" : File.separator);
	final static public String OUTPUT_DOC_LOCATION = TextConstants.get("smta.output.folder") + (TextConstants.get("smta.output.folder").endsWith(File.separator) ? "" : File.separator);
	
	static
	{
		File outputFolder = new File(OUTPUT_DOC_LOCATION);
		if (!outputFolder.exists() && !outputFolder.mkdirs())
			LOG.error("Unable to access or create output folder:" + OUTPUT_DOC_LOCATION);
	}
	
	static private void addDocument(PdfCopy destination, PdfReader documentToAdd, int pageOffset, ArrayList<List> master) throws BadPdfFormatException, IOException
	{
		documentToAdd.consolidateNamedDestinations();
		int n = documentToAdd.getNumberOfPages();
		List bookmarks = SimpleBookmark.getBookmark(documentToAdd);
		if (bookmarks != null)
		{
			if (pageOffset != 0)
				SimpleBookmark.shiftPageNumbers(bookmarks, pageOffset, null);

			master.addAll(bookmarks);
		}
				
		PdfImportedPage page;
		for (int i = 0; i < n;) {
			++i;
			page = destination.getImportedPage(documentToAdd, i);
			destination.addPage(page);
		}
		PRAcroForm form = documentToAdd.getAcroForm();
		if (form != null) {
			destination.copyAcroForm(documentToAdd);
		}
	}	

	@Transactional(propagation=Propagation.SUPPORTS)	
	static public File generateSMTA(Order order) throws Exception
	{
		String acceptanceMethod = order.getAcceptanceMethod();
		List<String> validAcceptanceMethods = Arrays.asList(MgbmsService.acceptanceMethods);		
		if (!validAcceptanceMethods.contains(acceptanceMethod))
			throw new Exception("provided acceptanceMethod value is not supported: " + acceptanceMethod);
		
		Contact recipient = order.getContact();

		String sRecipientName = Helper.nullToBlank(recipient.getTitle(), " ") + recipient.getLastName() + " " + recipient.getFirstName();
		String sRecipientAddress = Helper.compileAddress(recipient.getAddress());
		String sProviderName = TextConstants.get("smtaField.SignProName");
		String formattedDate = Helper.df.format(order.getRequestdate());
		
		// populate main form
		PdfReader reader = new PdfReader(TEMPLATE_DOC_LOCATION + sMainFile);
		ByteArrayOutputStream mainPartOS = new ByteArrayOutputStream();
		PdfStamper stamper = new PdfStamper(reader, mainPartOS);
		AcroFields fields = stamper.getAcroFields();
		fields.setField("Recipient", sRecipientName + "\n" + sRecipientAddress + "\n" + Helper.nullToBlank(recipient.getTel(), "\n") + Helper.nullToBlank(recipient.getEmail(), "\n"));
		fields.setField("Provider", /*sProviderName + "\n" + */TextConstants.get("smtaField.Provider"));
		
		makeAllFieldsReadOnly(stamper);
		stamper.close();
		
		LOG.debug("main form populated");
				
		// populate annex 1 form
		ByteArrayOutputStream annex1OS = Annex1Generator.generateAnnex1(order);
		
//		// populate annex 2 and 3 form
//		reader = new PdfReader(TEMPLATE_DOC_LOCATION + sAnnexes2to4);
//		ByteArrayOutputStream annexesOS = new ByteArrayOutputStream();
//		stamper = new PdfStamper(reader, annexesOS);
//		fields = stamper.getAcroFields();
//		String materialList = "";
//		for (AssOrderAccession aoa : order.getAssOrderAccessions())
//		{
//			Accession accession = aoa.getAccession();
//			materialList += (materialList.length() == 0 ? "" : "\n") + accession.getItcCode() + "   [" + accession.getName() + "]   " + Helper.nullToBlank(accession.getScientificName());
//		}
//		fields.setField("AccessionList", materialList);			

		LOG.debug("annex 1 form populated");

		String sPaymentMethod = order.getPaymentMethod();
		ByteArrayOutputStream paymentMethodAnnexOS = new ByteArrayOutputStream();
		//if (sPaymentMethod != null)
		{
			boolean f611Method = sPaymentMethod.equals(MgbmsService.PAYMENT_METHOD_6_11);
			reader = new PdfReader(TEMPLATE_DOC_LOCATION + (!f611Method ? sAnnex2 : sAnnexes3and4));
			stamper = new PdfStamper(reader, paymentMethodAnnexOS);

			// populate annex4 form if required	
			if (f611Method)
			{
				fields = stamper.getAcroFields();
				fields.setField("Sign611RecName", sRecipientName);
				fields.setField("Sign611RecDate", formattedDate);
			}
			
			makeAllFieldsReadOnly(stamper);
			stamper.close();
		}
				
		int pageOffset = 0;
		ArrayList<List> master = new ArrayList<List>();
		ByteArrayOutputStream mainOS = new ByteArrayOutputStream();
		
		Document document = new Document(reader.getPageSizeWithRotation(1));
		PdfCopy writer = new PdfCopy(document, mainOS);
		document.open();
		
		// add main part of SMTA
		reader = new PdfReader(mainPartOS.toByteArray());
		addDocument(writer, reader, pageOffset, master);		
		pageOffset += reader.getNumberOfPages();
		
		LOG.debug("main part added");
	
		// now add appropriate version of page 8 (option 10)
		reader = new PdfReader(TEMPLATE_DOC_LOCATION + (acceptanceMethod.equals(MgbmsService.ACCEPTANCE_METHOD_SIGNATURE) ? sArticle10option1File : (acceptanceMethod.equals(MgbmsService.ACCEPTANCE_METHOD_SHRINKWRAP) ? sArticle10option2File : sArticle10option3File)));		
		if (acceptanceMethod != MgbmsService.ACCEPTANCE_METHOD_SHRINKWRAP)
		{
			ByteArrayOutputStream article10OS = new ByteArrayOutputStream();
			stamper = new PdfStamper(reader, article10OS);
			fields = stamper.getAcroFields();
			if (acceptanceMethod.equals(MgbmsService.ACCEPTANCE_METHOD_CLICKWRAP))
				fields.setField("clickWrap", "Yes"); 
			else
			{	// ACCEPTANCE_METHOD_SIGNATURE
				fields.setField("SignProName", sProviderName);
				fields.setField("SignProFullName", TextConstants.get("smtaField.SignProFullName"));
				fields.setField("SignProDate", formattedDate);
				fields.setField("SignRecName", sRecipientName);
				fields.setField("SignRecFullName", order.getAuthorisedOfficialName());
			}			
			makeAllFieldsReadOnly(stamper);
			stamper.close();
			reader = new PdfReader(article10OS.toByteArray());
		}
		addDocument(writer, reader, pageOffset, master);		
		pageOffset += reader.getNumberOfPages();
		
		LOG.debug("acceptance part added");

		// now add annexes 1 to 4
		reader = new PdfReader(annex1OS.toByteArray());
		addDocument(writer, reader, pageOffset, master);		
		pageOffset += reader.getNumberOfPages();
		if (paymentMethodAnnexOS != null)
		{
			reader = new PdfReader(paymentMethodAnnexOS.toByteArray());
			addDocument(writer, reader, pageOffset, master);		
			pageOffset += reader.getNumberOfPages();
		}
		
		LOG.debug("annexes added");
		
		if (!master.isEmpty())
			writer.setOutlines(master);

		document.close();
				
		File outputFile = new File(OUTPUT_DOC_LOCATION + order.getId() + "_" + SMTA_OUTPUT_FILENAME);
		
		addPageNumbersAndSaveToFile(new ByteArrayInputStream(mainOS.toByteArray()), outputFile);	
		return outputFile;
	}
	
	private static void addPageNumbersAndSaveToFile(InputStream documentIS, File outputFile) throws IOException, DocumentException
	{
		FileOutputStream result = new FileOutputStream(outputFile);

		LOG.debug("writing output doc to: " + outputFile.getPath());
		
        PdfReader reader = new PdfReader(documentIS);
        // Create a stamper
        PdfStamper stamper = new PdfStamper(reader, result);
        // Loop over the pages and add a header to each page
        int n = reader.getNumberOfPages();
        for (int i = 1; i <= n; i++)
            getHeaderTable(i, n).writeSelectedRows(0, -1, 34, 60, stamper.getOverContent(i));
        stamper.close();
	}

	public static File generateAddendum(Order order) throws Exception
	{
		String materialList = "";
		for (AssOrderAccession aoa : order.getAssOrderAccessions())
		{
			Accession accession = aoa.getAccession();
			if (accession.getImproved())
				materialList += (materialList.length() == 0 ? "" : "\n") + accession.getItcCode() + "   [" + accession.getName() + "]   " + Helper.nullToBlank("(original provider here)");
		}
		
		if (materialList.length() == 0)
			return null;
		
		Contact recipient = order.getContact();
		
		String sRecipientName = Helper.nullToBlank(recipient.getTitle(), " ") + recipient.getLastName() + " " + recipient.getFirstName();
//		String sRecipientAddress = Helper.compileAddress(recipient.getAddress());
		String sProviderName = TextConstants.get("smtaField.SignProName");
		String formattedDate = Helper.df.format(order.getRequestdate());
		
		// populate main form
		PdfReader reader = new PdfReader(TEMPLATE_DOC_LOCATION + sAddendumFile);
		ByteArrayOutputStream mainPartOS = new ByteArrayOutputStream();
		PdfStamper stamper = new PdfStamper(reader, mainPartOS);
		AcroFields fields = stamper.getAcroFields();

		fields.setField("SignProName", sProviderName);
		fields.setField("SignProDate", formattedDate);
		fields.setField("SignRecName", sRecipientName);
		
		fields.setField("AccessionsUDList", materialList);	
		
		makeAllFieldsReadOnly(stamper);
		stamper.close();
					
		int pageOffset = 0;
		ArrayList<List> master = new ArrayList<List>();
		
		Document document = new Document(reader.getPageSizeWithRotation(1));
		ByteArrayOutputStream mainOS = new ByteArrayOutputStream();
		PdfCopy writer = new PdfCopy(document, mainOS);
		document.open();
		
		// add main part of MTA
		reader = new PdfReader(mainPartOS.toByteArray());
		addDocument(writer, reader, pageOffset, master);		
		pageOffset += reader.getNumberOfPages();
		
		if (!master.isEmpty())
			writer.setOutlines(master);

		document.close();
		
		File outputFile = new File(OUTPUT_DOC_LOCATION + order.getId() + "_" + ADDENDUM_OUTPUT_FILENAME);
		addPageNumbersAndSaveToFile(new ByteArrayInputStream(mainOS.toByteArray()), outputFile);
		return outputFile;
	}
	
    /**
     * Create a header table with page X of Y
     * @param x the page number
     * @param y the total number of pages
     * @return a table that can be used as header
     */
    private static PdfPTable getHeaderTable(int x, int y) {
        PdfPTable table = new PdfPTable(1);
        table.setTotalWidth(500);
        table.setLockedWidth(true);
		PdfPCell cell = new PdfPCell(new Phrase(String.format("%d / %d", x, y), new Font(Font.TIMES_ROMAN, 11, Font.NORMAL)));
		cell.setBorder(0);
        cell.setHorizontalAlignment(Element.ALIGN_RIGHT);
        table.addCell(cell);
        return table;
    }
	
	private static void makeAllFieldsReadOnly(PdfStamper stamper)
	{
		stamper.setFormFlattening(true);
//		AcroFields acroFields = stamper.getAcroFields();
//		for (Object fieldName : acroFields.getFields().keySet())
//			acroFields.setFieldProperty((String) fieldName, "setfflags", PdfFormField.FF_READ_ONLY, null);
	}
	
}
